#include "millpdata.h"
#include <bcm/bcm.h>
#include <iostream>
#include <map>

enum MenuOption {
	MODIFY_PARAMETER = 1,
	MODIFY_WORK_OFFSET = 2,
	MODIFY_TOOL_OFFSET = 3,
	DISPLAY_PARAMETERS = 4,
	EXIT = 5
};

MenuOption menu()
{
	std::cout << "1. Modify parameter." << std::endl;
	std::cout << "2. Modify work offset." << std::endl;
	std::cout << "3. Modify tool offset." << std::endl;
	std::cout << "4. Display all parameters." << std::endl;
	std::cout << "5. Exit." << std::endl;
	int option;
	std::cin >> option;
	return static_cast<MenuOption>(option);
}

template <typename T, typename S>
void displayParameter(cnc::base::Parameter *param)
{
	T value;
	if (param->get(&value))
		std::cout << static_cast<S>(value) << std::endl;
	else
		std::cout << "---" << std::endl;
}

template <typename S, typename R>
void editParameter(cnc::base::Parameter *param, const char *name)
{
	R readValue;
	S storeValue;
	std::cout << name << ": ";
	std::cin >> readValue;
	storeValue = static_cast<S>(readValue);
	if (param->set(storeValue))
		std::cout << "Parameter modified." << std::endl;
	else
		std::cout << "Error" << std::endl;
}

void modifyParameter(cnc::base::MillPersistentData *pd)
{
	int i = 0;
	std::map<int, const char *> paramMap;
	cnc::base::Parameter *param;
	std::cout << "Parameter list:" << std::endl;
	for (const char *paramName = pd->getFirstParameterName();
		paramName != 0;
		paramName = pd->getNextParameterName()) {
			std::cout << i << ". " << paramName << ": ";
			param = pd->getParameter(paramName);
			switch (param->getDataType()) {
			case cnc::base::DT_INT8:
				displayParameter<int8_t, int16_t>(param);
				break;
			case cnc::base::DT_UINT8:
				displayParameter<uint8_t, uint16_t>(param);
				break;
			case cnc::base::DT_INT16:
				displayParameter<int16_t, int16_t>(param);
				break;
			case cnc::base::DT_UINT16:
				displayParameter<uint16_t, uint16_t>(param);
				break;
			case cnc::base::DT_INT32:
				displayParameter<int32_t, int32_t>(param);
				break;
			case cnc::base::DT_UINT32:
				displayParameter<uint32_t, uint32_t>(param);
				break;
			case cnc::base::DT_INT64:
				displayParameter<int64_t, int64_t>(param);
				break;
			case cnc::base::DT_UINT64:
				displayParameter<uint64_t, uint64_t>(param);
				break;
			case cnc::base::DT_FLOAT32:
				displayParameter<float, float>(param);
				break;
			case cnc::base::DT_FLOAT64:
				displayParameter<double, double>(param);
				break;
			default:
				std::cout << "unsupported" << std::endl;
			}
			paramMap[i] = paramName;
			i++;
	}
	int numParameters = paramMap.size();
	int paramNo;
	do {
		std::cout << "Enter parameter number: ";
		std::cin >> paramNo;
		if (numParameters <= paramNo) {
			std::cout << "There is no parameter " << paramNo << "; there are only ";
			std::cout << numParameters << " parameters (0-"
				<< numParameters - 1 << ")." << std::endl;
		}
	} while (numParameters <= paramNo);
	param = pd->getParameter(paramMap[paramNo]);
	switch (param->getDataType()) {
	case cnc::base::DT_INT8:
		editParameter<int8_t, int16_t>(param, paramMap[paramNo]);
		break;
	case cnc::base::DT_UINT8:
		editParameter<uint8_t, uint16_t>(param, paramMap[paramNo]);
		break;
	case cnc::base::DT_INT16:
		editParameter<int16_t, int16_t>(param, paramMap[paramNo]);
		break;
	case cnc::base::DT_UINT16:
		editParameter<uint16_t, uint16_t>(param, paramMap[paramNo]);
		break;
	case cnc::base::DT_INT32:
		editParameter<int32_t, int32_t>(param, paramMap[paramNo]);
		break;
	case cnc::base::DT_UINT32:
		editParameter<uint32_t, uint32_t>(param, paramMap[paramNo]);
		break;
	case cnc::base::DT_INT64:
		editParameter<int64_t, int64_t>(param, paramMap[paramNo]);
		break;
	case cnc::base::DT_UINT64:
		editParameter<uint64_t, uint64_t>(param, paramMap[paramNo]);
		break;
	case cnc::base::DT_FLOAT32:
		editParameter<float, float>(param, paramMap[paramNo]);
		break;
	case cnc::base::DT_FLOAT64:
		editParameter<double, double>(param, paramMap[paramNo]);
		break;
	}
}

void modifyWorkOffset(cnc::base::MillPersistentData *pd)
{
	const unsigned int numWorkOffsets = pd->getNumWorkOffsets();
	std::cout << "There are " << numWorkOffsets << " work offsets (0-"
		<< numWorkOffsets - 1 << ")." << std::endl;
	unsigned int offsetNo;
	do {
		std::cout << "Enter work offset number: ";
		std::cin >> offsetNo;
		if (numWorkOffsets <= offsetNo) {
			std::cout << "There is no offset " << offsetNo << "; there are only ";
			std::cout << numWorkOffsets << " work offsets (0-"
				<< numWorkOffsets - 1 << ")." << std::endl;
		}
	} while (numWorkOffsets <= offsetNo);
	cnc::base::WorkOffset *workOffset = pd->getWorkOffset(offsetNo);
	if (workOffset == 0)
		std::cout << "Could not retrieve work offset." << std::endl;
	const unsigned int numAxes = workOffset->getNumAxes();
	std::cout << "There are " << numAxes << " axes (0-" << numAxes - 1 << ")." << std::endl;
	unsigned int axisNo;
	do {
		std::cout << "Enter axis number (0-" << numAxes - 1 <<"): ";
		std::cin >> axisNo;
		if (numAxes <= axisNo) {
			std::cout << "There is no axis " << axisNo << "; there are only ";
			std::cout << numAxes << " axes (0-"
				<< numAxes - 1 << ")." << std::endl;
		}
	} while (numAxes <= axisNo);
	std::cout << "Enter axis offset value: ";
	int32_t offset;
	std::cin >> offset;
	if (workOffset->set(axisNo, offset) == false)
		std::cout << "Could not modify axis offset value" << std::endl;
}

void modifyToolOffset(cnc::base::MillPersistentData *pd)
{
	const unsigned int numToolOffsets = pd->getNumToolOffsets();
	std::cout << "There are " << numToolOffsets << " tool offsets (0-"
		<< numToolOffsets - 1 << ")." << std::endl;
	unsigned int offsetNo;
	do {
		std::cout << "Enter tool offset number: ";
		std::cin >> offsetNo;
		if (numToolOffsets <= offsetNo) {
			std::cout << "There is no offset " << offsetNo << "; there are only ";
			std::cout << numToolOffsets << " tool offsets (0-"
				<< numToolOffsets - 1 << ")." << std::endl;
		}
	} while (numToolOffsets <= offsetNo);
	cnc::base::ToolData *toolOffset = pd->getToolGeometry(offsetNo);
	if (toolOffset == 0)
		std::cout << "Could not retrieve tool offset." << std::endl;
	enum { DIAMETER = 1, HEIGHT };
	unsigned int option;
	do {
		std::cout << "1. Modify diameter." << std::endl;
		std::cout << "2. Modify height." << std::endl;
		std::cin >> option;
	} while (option != DIAMETER && option != HEIGHT);
	std::cout << "Enter " << (option == DIAMETER ? "diameter" : "height") << " offset value: ";
	int32_t offset;
	std::cin >> offset;
	switch (option) {
	case DIAMETER:
		if (toolOffset->setDiameter(offset) == false)
			std::cout << "Could not modify tool diameter offset value" << std::endl;
		break;
	case HEIGHT:
		if (toolOffset->setHeight(offset) == false)
			std::cout << "Could not modify tool height offset value" << std::endl;
		break;
	}
}

void displayAllParameters(cnc::base::MillPersistentData *pd)
{
	int i = 0;
	cnc::base::Parameter *param;
	std::cout << "Parameter list:" << std::endl;
	for (const char *paramName = pd->getFirstParameterName();
		paramName != 0;
		paramName = pd->getNextParameterName()) {
			std::cout << i << ". " << paramName << ": ";
			param = pd->getParameter(paramName);
			switch (param->getDataType()) {
			case cnc::base::DT_INT8:
				displayParameter<int8_t, int16_t>(param);
				break;
			case cnc::base::DT_UINT8:
				displayParameter<uint8_t, uint16_t>(param);
				break;
			case cnc::base::DT_INT16:
				displayParameter<int16_t, int16_t>(param);
				break;
			case cnc::base::DT_UINT16:
				displayParameter<uint16_t, uint16_t>(param);
				break;
			case cnc::base::DT_INT32:
				displayParameter<int32_t, int32_t>(param);
				break;
			case cnc::base::DT_UINT32:
				displayParameter<uint32_t, uint32_t>(param);
				break;
			case cnc::base::DT_INT64:
				displayParameter<int64_t, int64_t>(param);
				break;
			case cnc::base::DT_UINT64:
				displayParameter<uint64_t, uint64_t>(param);
				break;
			case cnc::base::DT_FLOAT32:
				displayParameter<float, float>(param);
				break;
			case cnc::base::DT_FLOAT64:
				displayParameter<double, double>(param);
				break;
			default:
				std::cout << "unsupported" << std::endl;
			}
			i++;
	}
}

int main()
{
	bcm::ComponentManager &compMgr = bcm::ComponentManager::instance();
	switch (compMgr.loadComponent(TEXT("milldatabase.dll"))) {
	case bcm::LC_NODLL:
		std::cout << "Could not load milldatabase.dll." << std::endl;
		return 1;
	case bcm::LC_NOCOMPONENT:
		std::cout << "No component contained in milldatabase.dll." << std::endl;
		return 1;
	case bcm::LC_ALREADYUSED:
		std::cout << "Component \"milldb\" already registered." << std::endl;
		return 1;
	case bcm::LC_OK:
		std::cout << "Component \"milldb\" properly loaded from milldatabase.dll." << std::endl;
		break;
	}
	bcm::Component *component;
	switch (compMgr.getComponent("milldb", &component)) {
	case bcm::GC_UNREGISTERED:
		std::cout << "Component \"milldb\" was never registered." << std::endl;
		return 1;
	case bcm::GC_NOCOMPONENT:
		std::cout << "Component \"milldb\" not available in DLL." << std::endl;
		return 1;
	case bcm::GC_INCOMPLETE:
		std::cout << "Component \"milldb\" incomplete." << std::endl;
		return 1;
	case bcm::GC_OK:
		std::cout << "Component \"milldb\" properly loaded." << std::endl;
		break;
	}
	bcm::Interface *iface;
	iface = component->getInterface("millpdata");
	if (iface == 0) {
		std::cout << "Could not get interface MillPersistentData." << std::endl;
		return 1;
	}
	cnc::base::MillPersistentData *pd = dynamic_cast<cnc::base::MillPersistentData *>(iface);
	if (iface == 0) {
		std::cout << "Retrieved interface is not MillPersistentData." << std::endl;
		return 1;
	}
	if (pd->load("mill.db") == false) {
		std::cout << "Could not load mill.db. Database will be reset." << std::endl;
		pd->reset();
	}
	int option;
	do {
		option = menu();
		switch (option) {
		case MODIFY_PARAMETER:
			modifyParameter(pd);
			break;
		case MODIFY_WORK_OFFSET:
			modifyWorkOffset(pd);
			break;
		case MODIFY_TOOL_OFFSET:
			modifyToolOffset(pd);
			break;
		case DISPLAY_PARAMETERS:
			displayAllParameters(pd);
			break;
		}
	} while (option != EXIT);
	pd->save("mill.db");
	return 0;
}

